<?php
// +----------------------------------------------------------------------
// | KITEGO-Admin「开箱即用」「人人全栈」
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2024 https://www.kitego.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed KITEGO并不是自由软件，未经许可不能去掉KITEGO相关版权
// +----------------------------------------------------------------------
// | Author: KITEGO Team <bd@kitego.cn>
// +----------------------------------------------------------------------

namespace kitego\middleware;

use think\Request;
use voku\helper\AntiXSS;

class XssFilterMiddleware
{
    /**
     * 配置参数
     *
     * @var array
     */
    protected $config = [
        // 高风险处理策略：block=拦截，log=仅日志
        'highRiskStrategy' => 'block',
        // 白名单字段：不进行检测
        'whiteListFields' => [],
        // 富文本字段：保留基本HTML标签
        'richTextFields' => []
    ];

    /**
     * 原始请求参数
     *
     * @var array
     */
    protected $originalParams;

    /**
     * 处理后的请求参数
     *
     * @var array
     */
    protected $processedParams;

    /**
     * AntiXSS实例
     *
     * @var AntiXSS
     */
    protected $antiXss;

    /**
     * 是否检测到高危XSS
     *
     * @var bool
     */
    protected $hasHighRiskXss = false;

    /**
     * 构造函数
     *
     * @param array $config 配置参数
     */
    public function __construct(array $config = [])
    {
        $this->config = array_merge($this->config, $config);
        $this->antiXss = new AntiXSS();
    }

    /**
     * 中间件处理逻辑
     *
     * @param Request $request
     * @param \Closure $next
     * @return mixed
     */
    public function handle(Request $request, \Closure $next)
    {
        $this->originalParams = $request->param();
        $this->processedParams = $this->originalParams;

        // 处理参数并检测XSS
        $this->processParameters();

        // 高危XSS拦截
        if ($this->hasHighRiskXss && $this->config['highRiskStrategy'] === 'block') {
            $this->logAttack('high_risk');

            return fail('请求包含不安全内容，已被系统拦截');
        }

        // 替换处理后的参数
        $request->param($this->processedParams);

        return $next($request);
    }

    /**
     * 处理所有参数
     */
    protected function processParameters()
    {
        $this->processedParams = $this->recursiveProcess($this->originalParams);
    }

    /**
     * 递归处理参数
     *
     * @param array $params
     * @param string $parentKey
     * @return array
     */
    protected function recursiveProcess($params, $parentKey = '')
    {
        $result = [];

        foreach ($params as $key => $value) {
            $fullKey = $parentKey ? "{$parentKey}.{$key}" : $key;

            // 白名单字段跳过处理
            if (in_array($fullKey, $this->config['whiteListFields'])) {
                $result[$key] = $value;
                continue;
            }

            // 数组递归处理
            if (is_array($value)) {
                $result[$key] = $this->recursiveProcess($value, $fullKey);
                continue;
            }

            // 非字符串类型直接返回
            if (!is_string($value)) {
                $result[$key] = $value;
                continue;
            }

            // 处理富文本字段（保留安全HTML）
            if (in_array($fullKey, $this->config['richTextFields'])) {
                $result[$key] = $this->cleanRichText($value, $fullKey);
            }

            // 处理普通字段（过滤所有HTML）
            else {
                $result[$key] = $this->cleanNormalText($value, $fullKey);
            }
        }

        return $result;
    }

    /**
     * 清理富文本（保留安全HTML）
     *
     * @param string $value
     * @param string $fieldKey
     * @return string
     */
    protected function cleanRichText($value, $fieldKey)
    {
        $originalValue = $value;
        // 使用默认配置清理富文本（保留安全HTML标签）
        $cleanValue = $this->antiXss->xss_clean($value);

        // 检测是否发现XSS
        if ($this->antiXss->isXssFound()) {
            $this->logAttack('rich_text', [
                'field' => $fieldKey,
                'original' => $originalValue,
                'cleaned' => $cleanValue
            ]);
        }

        return $cleanValue;
    }

    /**
     * 清理普通文本（过滤所有HTML）
     *
     * @param string $value
     * @param string $fieldKey
     * @return string
     */
    protected function cleanNormalText($value, $fieldKey)
    {
        $originalValue = $value;
        // 先清理HTML标签
        $strippedValue = strip_tags($value);
        // 再进行XSS过滤
        $cleanValue = $this->antiXss->xss_clean($strippedValue);

        // 检测是否发现XSS
        if ($this->antiXss->isXssFound()) {
            $this->hasHighRiskXss = true;
            $this->logAttack('normal_field', [
                'field' => $fieldKey,
                'original' => $originalValue,
                'cleaned' => $cleanValue
            ]);
        }

        return $cleanValue;
    }

    /**
     * 记录攻击日志（格式化为索引数组）
     *
     * @param string $riskType
     * @param array $extra
     */
    protected function logAttack($riskType, $extra = [])
    {
        $request = app('request');

        // 按要求格式化为索引数组 [0, 1, 2]
        $logData = [
            // 0: 基本信息
            date('Y-m-d H:i:s') . ' | ' . $request->ip() . ' | ' . $request->method() . ' | ' . $riskType,
            // 1: 请求URL
            'URL: ' . $request->url(),
            // 2: 参数及额外信息
            'Params: ' . json_encode($this->originalParams) . ' | Extra: ' . json_encode($extra)
        ];

        kg_file_put_contents("xss_attack_{$riskType}", $logData);
    }
}
